package com.towexin.application.activiti.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.towexin.application.activiti.domain.ActivitiesForm;
import com.towexin.application.activiti.domain.ApproveInfo;
import com.towexin.application.activiti.mapper.ActivitiesFormMapper;
import com.towexin.application.activiti.service.ActivitiesService;
import com.towexin.application.activiti.service.ApproveInfoService;
import com.towexin.application.activiti.utils.ActivitiesUtil;
import com.towexin.application.system.mapper.RoleMapper;
import com.towexin.application.system.mapper.UserMapper;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.identity.Authentication;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.text.DateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * TODO
 *
 * @author Towexin
 * @version 1.0
 * @date 2021/10/20 10:04
 */
@Service
@Transactional
public class ActivitiesServiceImpl extends ServiceImpl<ActivitiesFormMapper, ActivitiesForm> implements ActivitiesService {
    private static final Logger logger = LoggerFactory.getLogger(ActivitiesServiceImpl.class);

    /**
     * 流程定义和部署相关的存储服务
     */
    @Autowired
    private RepositoryService repositoryService;

    /**
     * 流程运行时相关的服务
     */
    @Autowired
    private RuntimeService runtimeService;

    /**
     * 节点任务相关操作接口
     */
    @Autowired
    private TaskService taskService;

    /**
     * 流程图生成器
     */
    @Autowired
    private ProcessDiagramGenerator processDiagramGenerator;

    /**
     * 历史记录相关服务接口
     */
    @Autowired
    private HistoryService historyService;

    @Autowired
    private ApproveInfoService approveInfoService;

    @Autowired
    private RoleMapper roleMapper;

    @Autowired
    private UserMapper userMapper;

    @Override
    public List<JSONObject> processDefinitionList() {
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery()
                .latestVersion().list();

        return processDefinitionList.stream().map((s) -> {
            JSONObject object = new JSONObject();
            Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(s.getDeploymentId()).singleResult();

            object.put("name", s.getName());
            object.put("businessName", deployment.getName());
            object.put("des", s.getDescription());
            object.put("version", String.valueOf(s.getVersion()));
            object.put("key", s.getKey());
            object.put("id", s.getId());
            object.put("deploymentId", s.getDeploymentId());
            object.put("time", DateFormat.getDateTimeInstance().format(deployment.getDeploymentTime()));
            return object;
        }).collect(Collectors.toList());
    }

    // 文件上传部署方式
    @Override
    public boolean inputProcessDefinition(MultipartFile file, String name) {
        try {
            Deployment deployment = repositoryService//获取流程定义和部署对象相关的Service
                    .createDeployment()//创建部署对象
                    .name(name)
                    .addInputStream(file.getOriginalFilename(), file.getInputStream())
                    .deploy();//完成部署
            logger.info("部署ID：【{}】", deployment.getId());
            logger.info("部署Name：【{}】", deployment.getName());
            logger.info("部署时间：【{}】", deployment.getDeploymentTime());

            return Boolean.TRUE;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Boolean.FALSE;
    }

    @Override
    public boolean xmlProcessDefinition(String bpmnId, String xml, String name) {
        try {
            //获取流程XML
            XMLInputFactory factory = XMLInputFactory.newFactory();
            Reader reader = new StringReader(xml);
            XMLStreamReader streamReader = factory.createXMLStreamReader(reader);
            BpmnXMLConverter converter = new BpmnXMLConverter();
            BpmnModel bpmnModel = converter.convertToBpmnModel(streamReader);

            Deployment deployment = repositoryService//获取流程定义和部署对象相关的Service
                    .createDeployment()//创建部署对象
                    .name(name)
                    .addBpmnModel(bpmnId + ".bpmn", bpmnModel)
                    .deploy();//完成部署
            logger.info("部署ID：【{}】", deployment.getId());
            logger.info("部署Name：【{}】", deployment.getName());
            logger.info("部署时间：【{}】", deployment.getDeploymentTime());

            return Boolean.TRUE;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return Boolean.FALSE;
    }

    @Override
    public boolean deleteProcessDefinition(String id) {
        repositoryService.deleteDeployment(id, Boolean.TRUE);
        return Boolean.TRUE;
    }

    @Override
    public boolean startActivities(String processDefinitionId, String jsonString) {
        logger.info("开启流程...");
        //设置发起人
        Authentication.setAuthenticatedUserId(SecurityUtils.getSubject().getPrincipal().toString());

        ProcessInstance instance = runtimeService.startProcessInstanceById(processDefinitionId);
        logger.info("启动流程实例成功:{}", instance);
        logger.info("流程实例ID:{}", instance.getId());
        logger.info("流程定义ID:{}", instance.getProcessDefinitionId());

        ActivitiesForm form = new ActivitiesForm(instance.getId(), jsonString);


        List<ApproveInfo> approveInfos = new ArrayList<>();

        approveInfos.add(new ApproveInfo()
                                 .processInstanceId(instance.getId())
                                 .userOrRole(SecurityUtils.getSubject().getPrincipal().toString())
                                 .remark("发起流程")
                                 .state(0)
                                 .timestamp(new Date().getTime())
                                 .processingTime(new Date()));

        approveInfos.addAll(getCurrentNextUserTaskAssign(instance.getId()).stream().map(s -> new ApproveInfo()
                .processInstanceId(instance.getId())
                .userOrRole(s.getString("user"))
                .remark(s.getString("name"))
                .state(1)
                .timestamp(new Date().getTime())
        ).collect(Collectors.toList()));

        boolean flag = save(form) && approveInfoService.saveBatch(approveInfos);
        if (flag) getMyTasks();
        return flag;
    }

    @Override
    public boolean deleteProcessInstance(String instanceId) {
        ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(instanceId).singleResult();
        if (processInstance == null) {
            //该流程实例已经完成了
            historyService.deleteHistoricProcessInstance(instanceId);
        } else {
            //该流程实例未结束的
            runtimeService.deleteProcessInstance(instanceId, "");
            historyService.deleteHistoricProcessInstance(instanceId);//(顺序不能换)
        }
        return removeById(instanceId) &&
                approveInfoService.remove(new QueryWrapper<ApproveInfo>().eq("process_instance_id", instanceId));
    }

    @Override
    public JSONObject getXml(String id) {
        BpmnModel bpmnModel = repositoryService.getBpmnModel(id);
        BpmnXMLConverter converter = new BpmnXMLConverter();

        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(id).singleResult();

        //把bpmnModel对象转换成字符
        byte[] bytes = converter.convertToXML(bpmnModel);
        JSONObject object = new JSONObject();
        object.put("xml", new String(bytes));
        object.put("businessName", repositoryService.createDeploymentQuery().deploymentId(processDefinition.getDeploymentId()).singleResult().getName());
        return object;
    }

    @Override
    public List<JSONObject> getMyTasks() {
        List<String> assigneeIds = roleMapper.selectMarkAndDefaultByUserName(SecurityUtils.getSubject().getPrincipal().toString());
        assigneeIds.add(SecurityUtils.getSubject().getPrincipal().toString());

        List<Task> tasks = taskService.createTaskQuery().taskAssigneeIds(assigneeIds).list();

        return tasks.stream().map(t -> {
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(t.getProcessInstanceId()).singleResult();

            if (assigneeIds.contains(processInstance.getStartUserId())) {
                logger.info("***************** 自己发起的任务，自动完成 ******************");
                doTask(t.getId(), Boolean.TRUE);
                return null;
            }

            String nickname = userMapper.getNicknameByUsernameString(processInstance.getStartUserId());

            JSONObject object = new JSONObject();
            object.put("id", t.getId());
            object.put("processDefinitionId", t.getProcessDefinitionId());
            object.put("processInstanceId", t.getProcessInstanceId());
            object.put("name", t.getName());
            object.put("user", nickname);
            object.put("business", nickname + "的【" + repositoryService.createDeploymentQuery().deploymentId(processInstance.getDeploymentId()).singleResult().getName() + "】申请单");
            object.put("startTime", DateFormat.getDateTimeInstance().format(processInstance.getStartTime()));
            return object;
        }).collect(Collectors.toList());

    }

    @Override
    public List<JSONObject> getMyHistoricProcess() {
        List<HistoricProcessInstance> historicProcessInstances = historyService
                .createHistoricProcessInstanceQuery()
                .startedBy(SecurityUtils.getSubject().getPrincipal().toString())
                .orderByProcessInstanceStartTime().desc().list();
        if (historicProcessInstances.size() == 0)
            return new ArrayList<>();

        List<String> assigneeIds = roleMapper.selectMarkAndDefaultByUserName(SecurityUtils.getSubject().getPrincipal().toString());
        assigneeIds.add(SecurityUtils.getSubject().getPrincipal().toString());

        return historicProcessInstances.stream().map(h -> {
            List<Task> tasks = taskService.createTaskQuery().processInstanceId(h.getId()).list();
            if (tasks.size() > 0)
                tasks.forEach(t -> {
                    if (t != null) {
                        if (assigneeIds.contains(t.getAssignee()))
                            doTask(t.getId(), Boolean.TRUE);
                    }
                });

            JSONObject object = new JSONObject();
            object.put("processDefinitionId", h.getProcessDefinitionId());
            object.put("id", h.getId());
            object.put("name", repositoryService.createDeploymentQuery().deploymentId(h.getDeploymentId()).singleResult().getName());
            object.put("startTime", DateFormat.getDateTimeInstance().format(h.getStartTime()));
            object.put("endTime", h.getEndTime() != null ? DateFormat.getDateTimeInstance().format(h.getEndTime()) : "");
            ProcessInstance processInstance = runtimeService.createProcessInstanceQuery().processInstanceId(h.getId()).singleResult();
            if (processInstance == null) {
                object.put("state", 1);
            } else {
                object.put("state", 0);
            }
            return object;
        }).collect(Collectors.toList());

    }

    @Override
    public boolean doTask(String id, boolean state) {
        List<String> assigneeIds = roleMapper.selectMarkAndDefaultByUserName(SecurityUtils.getSubject().getPrincipal().toString());
        assigneeIds.add(SecurityUtils.getSubject().getPrincipal().toString());

        logger.info("流程审批，(办理人)taskAssignee:{}", assigneeIds.toString());

        Map<String, Object> map = new HashMap<>();

        ApproveInfo approveInfo = new ApproveInfo();

        String processInstanceId = taskService.createTaskQuery().taskId(id).singleResult().getProcessInstanceId();

        if (state) {
            map.put("state", "ok");
            approveInfo.setState(2);
            approveInfo.setProcessingTime(new Date());
            logger.info("【同意】");
        } else {
            map.put("state", "no");
            approveInfo.setState(3);
            approveInfo.setProcessingTime(new Date());
            logger.info("【拒绝】");
        }
        taskService.complete(id, map);

        List<JSONObject> assigns = getCurrentNextUserTaskAssign(processInstanceId);
        List<ApproveInfo> approveInfos = new ArrayList<>();
        if (assigns.size() == 0) {
            // 流程结束
            approveInfos.add(new ApproveInfo()
                                     .processInstanceId(processInstanceId)
                                     .userOrRole("process_end")
                                     .remark(state ? "ok" : "no")
                                     .state(4)
                                     .timestamp(new Date().getTime())
                                     .processingTime(new Date()));
        } else {
            approveInfos.addAll(assigns.stream().map(s -> {
                ApproveInfo ai = approveInfoService.getOne(new QueryWrapper<ApproveInfo>()
                                                                   .eq("process_instance_id", processInstanceId)
                                                                   .eq("user_or_role", s.getString("user"))
                                                                   .eq("state", 1));
                if (ai == null)
                    ai = new ApproveInfo()
                            .processInstanceId(processInstanceId)
                            .userOrRole(s.getString("user"))
                            .remark(s.getString("name"))
                            .state(1)
                            .timestamp(new Date().getTime());
                return ai;
            }).collect(Collectors.toList()));
        }

        return approveInfoService.update(
                approveInfo, new QueryWrapper<ApproveInfo>()
                        .eq("process_instance_id", processInstanceId)
                        .in("user_or_role", assigneeIds)
                        .ne("state", 0)) &&
                approveInfoService.saveOrUpdateBatch(approveInfos);
        //return false;
    }

    @Override
    public void getFlowChart(HttpServletResponse response, String processDefinitionId, String resType) {
        //获取流程定义信息
        ProcessDefinition processDefinition = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionId)
                .singleResult();

        InputStream resourceAsStream = null;
        if ("image".equals(resType)) {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinitionId);
            resourceAsStream = processDiagramGenerator.generateDiagram(bpmnModel, "宋体", "微软雅黑", "png");
        } else if ("xml".equals(resType)) {
            resourceAsStream = repositoryService
                    .getResourceAsStream(processDefinition.getDeploymentId(), processDefinition.getResourceName());
        }
        try {

            // 输出资源内容到相应对象
            byte[] b = new byte[1024];
            int len;
            while ((len = resourceAsStream.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
            response.getOutputStream().flush();
        } catch (Exception e) {
            logger.error("输出异常！", e);
        } finally { // 流关闭
            try {
                assert resourceAsStream != null;
                resourceAsStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void showImg(String id, HttpServletResponse response) {
        /*
         * 参数校验
         */
        logger.info("查看完整流程图！流程实例ID:{}", id);
        if (StringUtils.isBlank(id)) return;


        /*
         *  获取流程实例
         */
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(id).singleResult();
        if (processInstance == null) {
            logger.error("流程实例ID:{}没查询到流程实例！", id);
            return;
        }

        // 根据流程对象获取流程对象模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());


        /*
         *  查看已执行的节点集合
         *  获取流程历史中已执行节点，并按照节点在流程中执行先后顺序排序
         */
        // 构造历史流程查询
        HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(id);
        // 查询历史节点
        List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();
        if (historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
            logger.info("流程实例ID:{}没有历史节点信息！", id);
            outputImg(response, bpmnModel, null, null);
            return;
        }
        // 已执行的节点ID集合(将historicActivityInstanceList中元素的activityId字段取出封装到executedActivityIdList)
        List<String> executedActivityIdList = historicActivityInstanceList.stream().map(HistoricActivityInstance::getActivityId).collect(Collectors.toList());

        /*
         *  获取流程走过的线
         */
        // 获取流程定义
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService).getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
        List<String> flowIds = ActivitiesUtil.getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);


        /*
         * 输出图像，并设置高亮
         */
        outputImg(response, bpmnModel, flowIds, executedActivityIdList);
    }

    @Override
    public List<ApproveInfo> getTasks(String processInstanceId) {
        return approveInfoService.getApproveInfosByProcessInstanceId(processInstanceId);
    }

    /**
     * <p>输出图像</p>
     *
     * @param response               响应实体
     * @param bpmnModel              图像对象
     * @param flowIds                已执行的线集合
     * @param executedActivityIdList void 已执行的节点ID集合
     * @author FRH
     * @time 2018年12月10日上午11:23:01
     * @version 1.0
     */
    private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) {
        InputStream imageStream = null;
        try {
            imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png");
            // 输出资源内容到相应对象
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
            response.getOutputStream().flush();
        } catch (Exception e) {
            logger.error("流程图输出异常！", e);
        } finally { // 流关闭
            try {
                assert imageStream != null;
                imageStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public List<JSONObject> getCurrentNextUserTaskAssign(String processInstanceId) {
        List<Task> tasks = taskService.createTaskQuery().processInstanceId(processInstanceId).list();

        if (tasks.size() == 0) return new ArrayList<>();

        List<JSONObject> assigns = new ArrayList<>();
        tasks.forEach(t -> {
            BpmnModel bpmnModel = repositoryService.getBpmnModel(t.getProcessDefinitionId());
            Process process = bpmnModel.getMainProcess();

            //获取所有普通任务节点
            List<UserTask> UserTaskList = process.findFlowElementsOfType(UserTask.class);

            for (UserTask userTask : UserTaskList) {
                if (userTask.getId().equals(t.getTaskDefinitionKey())) {
                    JSONObject object = new JSONObject();
                    object.put("user", userTask.getAssignee());
                    object.put("name", userTask.getName());
                    assigns.add(object);
                }
            }
        });
        return assigns;
    }
}
